package net.zoneland.knowledge.service.impl;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.base.Preconditions;

import net.zoneland.knowledge.cache.RedisCacheUtil;
import net.zoneland.knowledge.dao.WorkApplyInfoMapper;
import net.zoneland.knowledge.enums.WorkStatusEnum;
import net.zoneland.knowledge.enums.WorkStatusOfSubTaskEnum;
import net.zoneland.knowledge.enums.WorkSubTaskNodeEnum;
import net.zoneland.knowledge.exceptions.WorkApplyException;
import net.zoneland.knowledge.model.LoginUserPojo;
import net.zoneland.knowledge.model.WorkApplyInfo;
import net.zoneland.knowledge.model.WorkSubTaskInfo;
import net.zoneland.knowledge.service.UniFlowService;
import net.zoneland.knowledge.service.WorkDoneStatusInfoService;
import net.zoneland.knowledge.service.WorkSubTaskHandleService;
import net.zoneland.knowledge.service.WorkSubTaskInternalService;
import net.zoneland.knowledge.service.bean.ResultInfo;
import net.zoneland.knowledge.utils.WorkApplyOperationUtils;
import net.zoneland.uniflow.client.TaskObject;
import net.zoneland.uniflow.client.UserInfo;
import static net.zoneland.knowledge.service.WorkSubTaskInternalService.GET_LOCK_FAILED;
import static net.zoneland.knowledge.service.WorkSubTaskInternalService.TASK_ID_BLANK_ERROR_MSG;

@Service
public class WorkSubTaskHandleServiceImpl implements WorkSubTaskHandleService {

    /**
     * LOG.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(WorkSubTaskHandleServiceImpl.class);

    /**
     * 可以上报的状态.
     */
    public static final EnumSet<WorkStatusEnum> CAN_REPORT_STATUS = EnumSet.of(WorkStatusEnum.SUB_ORG_USER_HANDLE,
                                                                                 WorkStatusEnum.SUB_ORG_USER_CONFIRM,
                                                                                 WorkStatusEnum.RESPONDER_APPROVE,
                                                                                 WorkStatusEnum.SUB_ORG_USER_CONFIRM);
    /**
     * 对下发的任务处理人来说完成的节点.
     */
    public static final EnumSet<WorkSubTaskNodeEnum> FINISH_NODE_FOR_HANDLER = EnumSet.of(WorkSubTaskNodeEnum.FINISH,
            WorkSubTaskNodeEnum.SUPERIOR_CONFIRM);
    /**
     * 工单信息数据接口.
     */
    @Autowired
    private WorkApplyInfoMapper workApplyInfoMapper;

    /**
     * 分任务服务接口
     */
    @Autowired
    private WorkSubTaskInternalService workSubTaskInternalService;

    /**
     * 已办状态表服务.
     */
    @Autowired
    private WorkDoneStatusInfoService workDoneStatusInfoService;

    /**
     * 流程业务接口.
     */
    @Autowired
    private UniFlowService uniFlowService;

    /**
     * redis工具类.
     */
    @Autowired
    private RedisCacheUtil redisCacheUtil;

    /**
     * 获取全局锁重试次数.
     */
    @Value("${work.lock.retry.count:3}")
    private int retryCount;
    /**
     * 获取全局锁等待时间毫秒.
     */
    @Value("${work.lock.retry.wait.time:1000}")
    private long retryWaitTime;
    /**
     * 智办流程的待办处理人员uid.
     */
    @Value("${work.todo.mock.userId:aihua}")
    private String mockUserId;
    /**
     * 智办流程的待办处理人员姓名.
     */
    @Value("${work.todo.mock.userName:艾华}")
    private String mockUserName;

    /**
     * 分派分任务.
     * <p>这个方法是针对部分分任务的分派，不是整个工单一起分派</p>
     * <p>处理逻辑：
     * <li>当前工单状态保持不变（数据、流程都不变）</li>
     * <li>创建新的子工单及其分任务，并给子工单发起流程</li>
     * <li>被分派的分任务（当前工单的）的状态要变成下发状态（原来可能是在待确认状态）</li>
     * </p>
     *
     * @param currentApplyInfo 当前工单信息
     * @param subTasks         要分派的分任务列表，可能只是当前工单下的部分分任务
     * @param dispatchUserNames    要分派的人员名单（要分配给这些人），此字段由选人框产生
     * @param opinion          处理意见
     * @param user             处理人
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void dispatch(final WorkApplyInfo currentApplyInfo, final List<WorkSubTaskInfo> subTasks, final String dispatchUserNames, final String opinion, final LoginUserPojo user) {
        // 先把子工单和分任务创建起来，包括流程也发起了
        workSubTaskInternalService.createSubTask(currentApplyInfo, subTasks, dispatchUserNames, user);

        // 把当前分任务状态改成下发状态（因为刚刚分派下去了）
        workSubTaskInternalService.updateSubTasksNode(subTasks, WorkSubTaskNodeEnum.ISSUED);

        // 写处理记录，但是流程都没动、写一条处理记录是否合适？
        workApplyInfoMapper.insertWorkApproveOpinion(WorkApplyOperationUtils.buildApproveOpinion(opinion, user, currentApplyInfo, "分派"));
    }

    /**
     * 上报分任务.
     *  @param currentApplyInfo 当前工单
     * @param subTasks         要上报的分任务列表，可能只是当前工单下的部分分任务
     * @param opinion          处理意见
     * @param user             处理人
     * @param request
     * @return 三元组，依次为：当前工单，已经上报了的分任务列表，父工单
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Triple<WorkApplyInfo, List<WorkSubTaskInfo>, WorkApplyInfo> report(final WorkApplyInfo currentApplyInfo, final List<WorkSubTaskInfo> subTasks, final String taskId, final String opinion, final LoginUserPojo user, HttpServletRequest request) {
        List<TaskObject> uniflowTasks = null;
        try {
            Preconditions.checkArgument(StringUtils.isNotBlank(taskId), TASK_ID_BLANK_ERROR_MSG);
            workSubTaskInternalService.checkDataBeforeApprove(currentApplyInfo, null, user);
            Preconditions.checkArgument(CAN_REPORT_STATUS.contains(currentApplyInfo.getStatus()),  "当前环节不能上报！");
            Preconditions.checkArgument(WorkApplyOperationUtils
                            .lockWorkApplyInfo(redisCacheUtil, currentApplyInfo.getParentId(), retryCount, 0, retryWaitTime),
                    GET_LOCK_FAILED);

            //上报分任务时需要上报次数加1
            subTasks.stream().forEach(item -> item.setReportCount(item.getReportCount() + 1));
            //处理上报附件，保存附件在reportSubTasks中
            workSubTaskInternalService.handleReportAttachments(subTasks, user, request);

            // 更新分任务信息，以及分任务的父任务，只涉及数据、不涉及流程，必须在更新工单数据之前（因为工单状态依赖于分任务的状态）
            workSubTaskInternalService.reportSubTasks(subTasks);

            // 更新父流程信息，将父流程提交到责任人审批环节
            Pair<WorkApplyInfo, List<TaskObject>> parentApplySubmitResult =
                workSubTaskInternalService.submitParentApplyWhenSubApplySubmit(currentApplyInfo, user, opinion);

            //分任务上报记录
            subTasks.forEach(task->{
                WorkApplyOperationUtils.addTaskOpinion(workApplyInfoMapper,task,opinion,user,String.format("上报（%s）",task.getName()));
            });

            uniflowTasks = parentApplySubmitResult.getRight();

            // 最后更新当前工单（顺序不能搞错，必须先更新分任务、再更新工单，因为工单状态依赖于分任务状态）
            workSubTaskInternalService.reportWorkApply(currentApplyInfo, taskId, subTasks, user, opinion);
            // 上报只影响当前工单和父工单
            workDoneStatusInfoService.dealWorkApplyDoneStatus(currentApplyInfo,parentApplySubmitResult.getLeft(), null, user);
            // 依次为：当前工单，已经上报了的分任务列表，父工单
            return Triple.of(currentApplyInfo, subTasks, parentApplySubmitResult.getLeft());
        } catch (WorkApplyException e) {
            LOGGER.error("report WorkApplyException taskId:{}, subTaskId:{}", taskId, subTasks.get(0).getId());
            //对于父流程的一些已完成的任务，应该要回退
            WorkApplyOperationUtils.rollbackTasks(uniFlowService, uniflowTasks);
            // 可能是业务校验异常，前端会用到异常message，
            throw e;
        } catch (Exception e) {
            LOGGER.error("report Exception taskId:{}, subTaskId:{}", taskId, subTasks.get(0).getId());
            //对于父流程的一些已完成的任务，应该要回退
            WorkApplyOperationUtils.rollbackTasks(uniFlowService, uniflowTasks);
            throw new WorkApplyException("分任务子流程-组织人员处理上报异常", e);
        } finally {
            if (currentApplyInfo != null && StringUtils.isNotBlank(currentApplyInfo.getParentId())) {
                redisCacheUtil.hdel(currentApplyInfo.getParentId());
            }
        }
    }

    /**
     * 退回分任务.
     * <li>1、修改分任务状态</li>
     * <li>2、分任务的父级分任务的状态：保持不变</li>
     * <li>3、分任务的工单：要到退回组织人员办理（此工单没有再分派）或组织人员确认（此工单已经分派）环节；<br/>
     * 但如果分任务的工单已经在组织人员办理（此工单没有再分派）或组织人员确认（此工单已经分派）环节，那么就无须处理</li>
     * <li>4、父级工单（当前工单）：保持不变</li>
     *
     * @param currentApplyInfo 当前工单
     * @param subTasks         要退回的分任务列表。注意，这个要退回的分任务是属于子工单的、不是属于当前工单的
     * @param opinion          处理意见
     * @param user             处理人
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<WorkSubTaskInfo> reject(final WorkApplyInfo currentApplyInfo, final List<WorkSubTaskInfo> subTasks, final String opinion, final LoginUserPojo user) {
        // 先更新分任务状态，注意此处父任务的状态不变
        workSubTaskInternalService.rejectSubTasks(subTasks);

        // 接下来处理分任务所属的子工单，找出既不是组织人员办理、也不是组织人员确认的工单，需要把这些工单的流程退回
        Collection<WorkApplyInfo> workApplyInfoListBySubTaskList = getWorkApplyInfoListBySubTaskList(subTasks);
        List<WorkApplyInfo> workApplyListNeedToReject = workApplyInfoListBySubTaskList.stream()
                .filter(workApplyInfo ->
                        workApplyInfo.getStatus() != WorkStatusEnum.SUB_ORG_USER_HANDLE && workApplyInfo.getStatus() != WorkStatusEnum.SUB_ORG_USER_CONFIRM)
                .collect(Collectors.toList());

        WorkApplyOperationUtils.addOpinions(workApplyInfoMapper,workApplyInfoListBySubTaskList,opinion,user, "退回");
        for (WorkApplyInfo workApplyInfo : workApplyListNeedToReject) {
            // 要退回到的目标节点
            WorkStatusEnum targetStatus = workApplyInfo.isDispatched() ? WorkStatusEnum.SUB_ORG_USER_CONFIRM
                    : WorkStatusEnum.SUB_ORG_USER_HANDLE;

            //更新分任务对应工单信息
            workApplyInfo.setUpdateTime(DateTime.now().toDate());
            workApplyInfo.setHandlerUid(workApplyInfo.getAssignUserId());
            workApplyInfo.setHandlerName(workApplyInfo.getAssignUserName());
            workApplyInfo.setStatus(targetStatus);
            workApplyInfoMapper.updateWorkApplyInfo(workApplyInfo);
            // 目标节点处理人
            final UserInfo[] subTaskHandlers = WorkApplyOperationUtils.getHandlerArray(workApplyInfo.getAssignUserId(),
                                                                                       workApplyInfo.getAssignUserName());
            // 当前处理人
            final UserInfo handler = new UserInfo(user.getUserID(), user.getUserName());
            // 处理流程
            uniFlowService.sendTheApplyToNode(workApplyInfo, targetStatus.getValue(), subTaskHandlers, handler, opinion);
        }
        // 判断当前待办是否需要挂起
        workSubTaskInternalService.checkNeedSuspendTask(currentApplyInfo, subTasks);

        final List<WorkApplyInfo> allWorkApplyInfos = getAllSubWorkApply(currentApplyInfo, workApplyInfoListBySubTaskList);
        //退回分任务，影响当前工单和子工单
        workDoneStatusInfoService.dealWorkApplyDoneStatus(currentApplyInfo, null, allWorkApplyInfos, user);
        // 对象的状态已经被更新掉、是最新状态，可以return回去
        return subTasks;
    }

    /**
     * 确认分任务.
     * <p>1、修改分任务状态</p>
     * <p>2、处理分任务所属工单的流程：如果此工单下所有分任务都确认了，此工单需要提交</p>
     * <p>3、修改父级分任务状态：如果父级分任务的所有下级分任务都确认了，表示此父级分任务可以上报了</p>
     * <p>4、处理父级工单：似乎什么都不用处理，因为只是确认下级分任务、本级分任务还没上报</p>
     *
     * @param currentApplyInfo 当前工单
     * @param subTasks         要确认的分任务列表。注意，这个要确认的分任务是属于子工单的、不是属于当前工单的
     * @param opinion          处理意见
     * @param user             处理人
     * @return 二元组，依次为：已经确认的分任务列表，收到影响的父级分任务列表
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Pair<List<WorkSubTaskInfo>, List<WorkSubTaskInfo>> confirm(final WorkApplyInfo currentApplyInfo, final List<WorkSubTaskInfo> subTasks, final String opinion, final LoginUserPojo user) {
        // 先更新分任务状态，包括父任务的状态也一起更新了, 还有合并附件的功能
        final List<WorkSubTaskInfo> impactedParentSubTaskList =
                workSubTaskInternalService.confirmSubTasks(currentApplyInfo, subTasks, user);

        // 接下来处理分任务所属的子工单，找出所有分任务都完成的子工单，需要把这些子工单的流程结束掉
        final Collection<WorkApplyInfo> workApplyInfoListBySubTaskList = getWorkApplyInfoListBySubTaskList(subTasks);
        final List<WorkApplyInfo> workApplyListNeedToFinish = workApplyInfoListBySubTaskList.stream()
                .filter(workApplyInfo -> WorkStatusOfSubTaskEnum.SUBTASKS_ALL_FINISHED == workSubTaskInternalService.calculateWorkApplyStatusBySubTasks(workApplyInfo))
                .collect(Collectors.toList());

        //任务记录
        WorkApplyOperationUtils.addOpinions(workApplyInfoMapper,workApplyInfoListBySubTaskList,opinion,user, "确认");

        // 把子工单流程结束掉
        WorkApplyOperationUtils.finishConfirmSubTask(workApplyInfoMapper, uniFlowService,
                workApplyListNeedToFinish, opinion, user);

        // 如果是主工单，在所有分任务都确认的情况下也要结束掉
        if(currentApplyInfo.getStatus() == WorkStatusEnum.FINISH_CONFIRM) {
            UserInfo currentUserInfo = new UserInfo(user.getUserID(), user.getUserName());
            ResultInfo mainApplyResultInfo = uniFlowService.jumpToNode(currentApplyInfo.getPiid(), WorkStatusEnum.FINISH_CONFIRM.getValue(),
                    new UserInfo[]{currentUserInfo}, currentUserInfo, "送办结确认", opinion);
            if(mainApplyResultInfo.isResult()) {
                // 要把最新tiid传回去
                currentApplyInfo.setTiid(((TaskObject[])mainApplyResultInfo.getData())[0].getTiid());
            } else {
                throw new WorkApplyException("处理主工单流程出错，主工单id[" + currentApplyInfo.getId() + "]处理人[" + user.getUserID() + "]");
            }
        }
        final List<WorkApplyInfo> allWorkApplyInfos = getAllSubWorkApply(currentApplyInfo, workApplyInfoListBySubTaskList);

        //确认分任务会影响子工单和当前工单
        workDoneStatusInfoService.dealWorkApplyDoneStatus(currentApplyInfo, null, allWorkApplyInfos,  user);

        // 对象的状态已经被更新掉、是最新状态，可以return回去
        return Pair.of(subTasks, impactedParentSubTaskList);
    }

    /**
     * 获取所有子工单信息.
     * @param currentApplyInfo 当前工单
     * @param workApplyInfoListBySubTaskList 当前工单子工单
     * @return
     */
    private List<WorkApplyInfo> getAllSubWorkApply(final WorkApplyInfo currentApplyInfo,
                                                  final Collection<WorkApplyInfo> workApplyInfoListBySubTaskList) {

        final Set<String> workApplyInfoIds = workApplyInfoListBySubTaskList.stream()
            .map(WorkApplyInfo::getId)
            .collect(Collectors.toSet());
        // 从数据库查询的子工单(排除当前参数的子工单)
        final List<WorkApplyInfo> subApplysInDb =
                workApplyInfoMapper.findWorkApplyInfoByParentIdExcludeIds(currentApplyInfo.getId(), workApplyInfoIds);
        // 数据库查询的子工单信息 + 参数中的子工单数据为所有子工单数据。
        return Stream.concat(subApplysInDb.stream(), workApplyInfoListBySubTaskList.stream()).collect(Collectors.toList());
    }

    /**
     * 根据分任务列表获得对应的工单集合。
     * <p>获得工单列表是去重的</p>
     *
     * @param subTasks 分任务列表
     * @return 分任务对应的工单集合
     */
    private Collection<WorkApplyInfo> getWorkApplyInfoListBySubTaskList(final List<WorkSubTaskInfo> subTasks) {
        // 搞个用来存放工单的容器Map用来去重
        Map<String, WorkApplyInfo> subApplyInfoMap = new HashMap<>();
        subTasks.forEach(
                // 以id为key，没有就从数据库里查塞到Map里
                st -> subApplyInfoMap.computeIfAbsent(st.getWorkApplyId(), id -> {
                    WorkApplyInfo workApplyInfo = workApplyInfoMapper.findWorkApplyInfoById(id);
                    workApplyInfo.setSubTasks(Arrays.asList(st));
                    return workApplyInfo;
                })

        );
        return subApplyInfoMap.values();
    }
}
